using PathX.Collection;
using UnityEngine;
using System.Collections;
using System;
using FPLibrary;

namespace PathX
{
    public class CellMatrix : Matrix<Cell>
    {
        private FPVector _origin;
        private FPVector _start;
        private FPBB _bounds;
        private Fix64 _cellSize;
        public FPBB bounds => _bounds;
        public FPVector origin => _origin;
        public Fix64 cellSize => _cellSize;

        private CellMatrix(ICellMatrixConfiguration cfg) : base(cfg.sizeX, cfg.sizeZ)
        {
            Initialize(cfg);
        }
        
        private void Initialize(ICellMatrixConfiguration cfg)
        {
            _cellSize = cfg.cellSize;
            _origin = cfg.origin;
            _start = cfg.origin;
            _start.x -= this.columns * 0.5f * _cellSize;
            _start.z -= this.rows * 0.5f * _cellSize;

            _bounds = cfg.bounds;
        }

        private IEnumerator Populate()
        {
            var blockThreshold = Mathf.Max(0.5f, 0.01f);
            for (int x = 0; x < this.columns; x++)
            {
                for (int z = 0; z < this.rows; z++)
                {
                    var position = new FPVector(_start.x + (x * _cellSize) + (_cellSize / 2.0f), _origin.y, _start.z + (z * _cellSize) + (_cellSize / 2.0f));

                    position.y = origin.y;
                    var blocked = PhysicX.IsBlocked(position, blockThreshold);

                    this.rawMatrix[x, z] = CellFactory.Create(this, position, x, z, blocked);

                    yield return null;
                }
            }
        }
        
        private IEnumerator Populate(CellMatrixData data)
        {
            var accessor = data.GetAccessor();
            for (int x = 0; x < this.columns; x++)
            {
                for (int z = 0; z < this.rows; z++)
                {
                    var idx = (z * this.columns) + x;

                    var position = new FPVector(_start.x + (x * _cellSize) + (_cellSize / 2.0f), _origin.y, _start.z + (z * _cellSize) + (_cellSize / 2.0f));

                    var blocked = accessor.IsPermaBlocked(idx);

                    var cell = CellFactory.Create(this, position, x, z, blocked);

                    this.rawMatrix[x, z] = cell;

                    yield return null;
                }
            }
        }

        internal Cell GetCell(FPVector position, bool adjustToBounds)
        {
            var fx = (position.x - _start.x) / _cellSize;
            var fz = (position.z - _start.z) / _cellSize;

            var x = MathfFix64.FloorToInt(fx);
            var z = MathfFix64.FloorToInt(fz);
            
            //This is to handle the corner case where the position is on the exact right or top border
            if (!adjustToBounds && !InBounds(x, z) && _bounds.Contains(position))
            {
                adjustToBounds = true;
            }

            if (adjustToBounds)
            {
                x = AdjustColumnToBounds(x);
                z = AdjustRowToBounds(z);

                return this.rawMatrix[x, z];
            }

            return this[x, z];
        }
        
        internal MatrixBounds GetMatrixBounds(FPVector position, Fix64 radiusX, Fix64 radiusZ, Fix64 minOverlapToInclude, bool adjustToBounds)
        {
            Fix64 maxAdj = 0f;
            Fix64 minAdj = 0f;
            if (minOverlapToInclude != 0.0f)
            {
                //We only want to include a cell when it is overlapped by a certain amount. The minAdj is to cater for borderline cases, i.e. when radius exactly matches the overlap
                radiusX -= minOverlapToInclude;
                radiusZ -= minOverlapToInclude;
                minAdj = 0.00001f;
            }
            else
            {
                //We do not want to include a cell in the borderline case, i.e. when radius hits right on a cell boundary. If not adjusted cells along the positive axis will include the next cell in such cases.
                maxAdj = 0.00001f;
            }

            if (adjustToBounds)
            {
                return new MatrixBounds
                {
                    minColumn = AdjustColumnToBounds(MathfFix64.FloorToInt((position.x - _start.x - radiusX - minAdj) / _cellSize)),
                    minRow = AdjustRowToBounds(MathfFix64.FloorToInt( (position.z - _start.z - radiusZ - minAdj) / _cellSize)),
                    maxColumn = AdjustColumnToBounds(MathfFix64.FloorToInt((position.x - _start.x + radiusX - maxAdj) / _cellSize)),
                    maxRow = AdjustRowToBounds(MathfFix64.FloorToInt((position.z - _start.z + radiusZ - maxAdj) / _cellSize))
                };
            }

            return new MatrixBounds
            {
                minColumn = MathfFix64.FloorToInt((position.x - _start.x - radiusX - minAdj) / _cellSize),
                minRow = MathfFix64.FloorToInt((position.z - _start.z - radiusZ - minAdj) / _cellSize),
                maxColumn = MathfFix64.FloorToInt((position.x - _start.x + radiusX - maxAdj) / _cellSize),
                maxRow = MathfFix64.FloorToInt((position.z - _start.z + radiusZ - maxAdj) / _cellSize)
            };
        }
        
        public bool Contains(FPVector pos)
        {
            return _bounds.Contains(pos);
        }
        
        //更新特定范围内的cell
        internal void UpdateForEditor(Bounds extent, CellMatrixData data)
        {
            // var bounds = GetMatrixBounds(extent.center, extent.extents.x + (_cellSize * 2), extent.extents.z + (_cellSize * 2), 0.0f, true);
            //
            // if (data == null)
            // {
            //     var blockThreshold = Mathf.Max(_obstacleSensitivityRange, 0.01f);
            //     for (int x = bounds.minColumn; x <= bounds.maxColumn; x++)
            //     {
            //         for (int z = bounds.minRow; z <= bounds.maxRow; z++)
            //         {
            //             var cell = this.rawMatrix[x, z];
            //
            //             if (cell == null)
            //             {
            //                 var position = new FPVector(_start.x + (x * _cellSize) + (_cellSize / 2.0f), _origin.y, _start.z + (z * _cellSize) + (_cellSize / 2.0f));
            //
            //                 var blocked = PhysicX.IsBlocked(position, blockThreshold);
            //
            //                 this.rawMatrix[x, z] = CellFactory.Create(this, position, x, z, blocked);
            //             }
            //             else
            //             {
            //                 var cellPos = cell.position;
            //                 var blocked = PhysicX.IsBlocked(cellPos, blockThreshold);
            //
            //                 cell.UpdateState(blocked);
            //             }
            //         }
            //     }
            // }
            // else
            // {
            //     var accessor = data.GetAccessor();
            //
            //     //Init the cells
            //     for (int x = bounds.minColumn; x <= bounds.maxColumn; x++)
            //     {
            //         for (int z = bounds.minRow; z <= bounds.maxRow; z++)
            //         {
            //             var cell = this.rawMatrix[x, z];
            //
            //             if (cell == null)
            //             {
            //                 var idx = (z * this.columns) + x;
            //
            //                 var position = new Vector3(_start.x + (x * _cellSize) + (_cellSize / 2.0f), _origin.y, _start.z + (z * _cellSize) + (_cellSize / 2.0f));
            //
            //                 var blocked = accessor.IsPermaBlocked(idx);
            //
            //                 cell = CellFactory.Create(this, position, x, z, blocked);
            //
            //                 this.rawMatrix[x, z] = cell;
            //             }
            //         }
            //     }
            // }
        }
        
        
        /// <summary>
        /// Creates a cell matrix based on the given configuration.
        /// </summary>
        /// <param name="cfg">The configuration.</param>
        /// <returns>The matrix</returns>
        public static CellMatrix Create(ICellMatrixConfiguration cfg)
        {
            var matrix = new CellMatrix(cfg);

            var iter = matrix.Populate();
            while (iter.MoveNext())
            {
            }

            return matrix;
        }

        /// <summary>
        /// Creates a cell matrix based on the given configuration and stored data.
        /// </summary>
        /// <param name="cfg">The configuration.</param>
        /// <param name="data">The data.</param>
        /// <returns>The matrix</returns>
        public static CellMatrix Create(ICellMatrixConfiguration cfg, CellMatrixData data)
        {
            var matrix = new CellMatrix(cfg);

            var iter = matrix.Populate(data);
            while (iter.MoveNext())
            {
            }

            return matrix;
        }
        
        internal static CellMatrix CreateForEditor(ICellMatrixConfiguration cfg)
        {
            var matrix = new CellMatrix(cfg);

            return matrix;
        }
    }
    
    /// <summary>
    /// Represents a matrix boundary consisting of min/max values for matrix indexes.
    /// </summary>
    public struct MatrixBounds
    {
        /// <summary>Represent an empty bounds</summary>
        public static readonly MatrixBounds nullBounds = new MatrixBounds(-1, -1, -2, -2);
        /// <summary>The minimum column index</summary>
        public int minColumn;
        /// <summary>The maximum column index</summary>
        public int maxColumn;
        /// <summary>The minimum row index</summary>
        public int minRow;
        /// <summary>The maximum row index</summary>
        public int maxRow;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Apex.DataStructures.MatrixBounds" /> struct.
        /// </summary>
        /// <param name="minColumn">The minimum column index.</param>
        /// <param name="minRow">The minimum row index.</param>
        /// <param name="maxColumn">The maximum column index.</param>
        /// <param name="maxRow">The maximum row index.</param>
        public MatrixBounds(int minColumn, int minRow, int maxColumn, int maxRow)
        {
            this.minColumn = minColumn;
            this.minRow = minRow;
            this.maxColumn = maxColumn;
            this.maxRow = maxRow;
        }

        /// <summary>
        /// Gets a value indicating whether this bounds is empty, i.e. min &gt; max.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance is empty; otherwise, <c>false</c>.
        /// </value>
        public bool isEmpty => this.minColumn > this.maxColumn || this.minRow > this.maxRow;

        /// <summary>Gets the number of columns.</summary>
        /// <value>The columns.</value>
        public int columns => this.maxColumn - this.minColumn + 1;

        /// <summary>Gets the number of rows.</summary>
        /// <value>The rows.</value>
        public int rows => this.maxRow - this.minRow + 1;

        /// <summary>
        /// Combines two bounds to create a new bounds that covers the area of both plus any area between them.
        /// If one <see cref="P:Apex.DataStructures.MatrixBounds.isEmpty" /> it will return the other.
        /// </summary>
        /// <param name="first">The first bounds.</param>
        /// <param name="second">The second bounds.</param>
        /// <returns>A new bounds that covers the area of both plus any area between them.</returns>
        public static MatrixBounds Combine(MatrixBounds first, MatrixBounds second)
        {
            if (first.isEmpty)
                return second;
            return second.isEmpty ? first : new MatrixBounds(Math.Min(first.minColumn, second.minColumn), Math.Min(first.minRow, second.minRow), Math.Max(first.maxColumn, second.maxColumn), Math.Max(first.maxRow, second.maxRow));
        }

        /// <summary>Adjusts the column to bounds.</summary>
        /// <param name="column">The column index.</param>
        /// <returns>The index adjusted to bounds</returns>
        public int AdjustColumnToBounds(int column)
        {
            if (column < this.minColumn)
                return this.minColumn;
            return column > this.maxColumn ? this.maxColumn : column;
        }

        /// <summary>Adjusts the row to bounds.</summary>
        /// <param name="row">The row index.</param>
        /// <returns>The index adjusted to bounds</returns>
        public int AdjustRowToBounds(int row)
        {
            if (row < this.minRow)
                return this.minRow;
            return row > this.maxRow ? this.maxRow : row;
        }

        /// <summary>
        /// Determines whether the specified matrix coordinates are contained in this bounds.
        /// </summary>
        /// <param name="column">The column.</param>
        /// <param name="row">The row.</param>
        /// <returns><c>true</c> if contained; otherwise <c>false</c></returns>
        public bool Contains(int column, int row) => column >= this.minColumn && column <= this.maxColumn && row >= this.minRow && row <= this.maxRow;

        /// <summary>
        /// Determines whether this bounds contains the other bounds.
        /// </summary>
        /// <param name="other">The other bounds.</param>
        /// <returns><c>true</c> if contained; otherwise <c>false</c></returns>
        public bool Contains(MatrixBounds other) => other.maxRow <= this.maxRow && other.minRow >= this.minRow && other.maxColumn <= this.maxColumn && other.minColumn >= this.minColumn;

        /// <summary>
        /// If looking at the matrix bounds as an array (see below) this will return the index in this array that a given pair of column,row values would represent.
        ///  _______________
        /// |_1_|_3_|_5_|_7_|
        /// |_0_|_2_|_4_|_6_|
        /// How a matrix bounds is represented as an array
        /// </summary>
        /// <param name="column">The column.</param>
        /// <param name="row">The row.</param>
        /// <returns></returns>
        public int IndexOf(int column, int row) => column < this.minColumn || column > this.maxColumn || row < this.minRow || row > this.maxRow ? -1 : (column - this.minColumn) * this.rows + (row - this.minRow);

        /// <summary>
        /// Returns a <see cref="T:System.String" /> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String" /> that represents this instance.
        /// </returns>
        public override string ToString() => string.Format("xmin: {0}, zmin: {1}, xmax: {2}, zmax: {3}", (object)this.minColumn, (object)this.minRow, (object)this.maxColumn, (object)this.maxRow);
    }

    public static class CellFactory
    {
        public static Cell Create(CellMatrix matrix, FPVector original, int x, int z, bool blocked)
        {
            return new FlatCell(matrix, original, x, z, blocked);
        }
    }
}